
package application;

import java.util.ArrayList;
import java.util.PriorityQueue;
import optimizers.BruteForceHPOptimizer;
import optimizers.HPOptimizer;
import optimizers.HierarchicalHPOptimizer;
import parsers.ArffFileIO;
import search_algorithms.LocalSearcher;
import search_algorithms.SimulatedAnnealer;
import weka.core.Instances;
import Utilities.Utils;
import classifiers.Classifier;
import classifiers.KNNClassifier;
import classifiers.GaussianSVMClassifier;
import classifiers.RandomForestClassifier;
import data_structures.ConfigurationAccuracyPair;
import data_structures.DiscreteHP;
import data_structures.ContinuousHP;
import data_structures.HPConfiguration;
import data_structures.HyperParameter;
import java.lang.*;
import java.io.*;
import java.lang.System;
/**
 * Main application class for testing purposes.
 */
public class HPOptExperiment {

    /**
     * Print-out help and exit.
     */
    public static void usage() {
	System.err.println("WRONG ARGUMENTS: See README file.");
	System.exit(1);
    }

    public static void parseHPs (BufferedReader reader, int noOfHPs, 
				 double[] lowerBounds, double[] upperBounds, String[] hpTypes) {
	System.out.println(noOfHPs);
	try {
	    for (int j = 0; j < noOfHPs; ++j) {
		String line = reader.readLine();
		line = line.replaceAll("\\s+"," ");
		if (!line.equals("")) {
		    String[] values = line.split("\\s+");
		    lowerBounds[j] = Double.parseDouble(values[0]);
		    upperBounds[j] = Double.parseDouble(values[1]);
		    hpTypes[j] = values[2];
		}
	    }
	} catch (Exception e) {
	    System.err.println("Error parsing options file.");
	    usage();
	}
    }

    /**
     * Main application. Just run it to see the usage.
     */
    public static void main(String[] args) {
	
	// measure the time it takes to execute
	// the operations
	long startTime = -1; 
	long endTime = -2;

	Classifier classifier = null;
	HPOptimizer hpOptimizer = null;
	
	Instances dataset = null;
	HPConfiguration initialConfiguration = null;

	double lowerBound = Double.MAX_VALUE;
	double upperBound = (-1) * Double.MAX_VALUE; 

	boolean singleRunMode = false;
	boolean optimizationMode = false;
	boolean verbose = false;
	try {
	    for (int i = 0; i < args.length; ++i) {
		/////////////////////////////
		// Set verbose             //
		/////////////////////////////
		if (args[i].equals("-verbose")) {
		    System.out.println("Verbose mode.");
		    verbose = true;
		}
		
		/////////////////////////////
		// Set filename of dataset //
		/////////////////////////////	
		else if (args[i].equals("-file")) {
		    ++i;
		    dataset = ArffFileIO.read(args[i]);
		    if (verbose) {
			System.out.println("Using " + args[i] + " dataset.");
			System.out.println(dataset.numInstances() + " instances.");
		    }
		}

		//////////////////////////////
		// Single Run k-NN       //
		//////////////////////////////
		else if (args[i].equals("-knn")) {
			singleRunMode = true;
			++i;
			int k = Integer.parseInt(args[i]);
			HyperParameter[] hps = new HyperParameter[1];
			hps[0] = new DiscreteHP(k, k, k);
			initialConfiguration = new HPConfiguration(hps);
			classifier = new KNNClassifier(k,k);
		}

		//////////////////////////////
		// Single Run Gaussian SVM       
		//////////////////////////////
		else if (args[i].equals("-gsvm")) {
		    singleRunMode = true;
		    ++i;
		    double gammaVal = Double.parseDouble(args[i]);
		    ++i;
		    double cVal = Double.parseDouble(args[i]);
		    HyperParameter[] hps = new HyperParameter[2];
		    hps[0] = new ContinuousHP(gammaVal, gammaVal, gammaVal);
		    hps[1] = new ContinuousHP(cVal, cVal, cVal);
		    initialConfiguration = new HPConfiguration(hps);
		    classifier = new GaussianSVMClassifier(gammaVal, gammaVal, cVal, cVal);
		}

		//////////////////////////////
		// Single Run Random Forest
		//////////////////////////////
		else if (args[i].equals("-rf")) {
		    singleRunMode = true;
		    ++i;
		    int noOfTrees = Integer.parseInt(args[i]);
		    ++i;
		    int noOfFeaturesToUse = Integer.parseInt(args[i]);

		    HyperParameter[] hps = new HyperParameter[2];
		    hps[0] = new DiscreteHP(noOfTrees, noOfTrees, noOfTrees);
		    hps[1] = new DiscreteHP(noOfFeaturesToUse, noOfFeaturesToUse, 
					    noOfFeaturesToUse);
		    initialConfiguration = new HPConfiguration(hps);
		    classifier = new RandomForestClassifier(noOfTrees, noOfTrees, 
							    noOfFeaturesToUse, noOfFeaturesToUse);
		}

		///////////////////////////////
		// Optimization             //
		///////////////////////////////
		else if (args[i].equals("-optimize")) {
		    singleRunMode = false;
		    optimizationMode = true;		    
		    ++i;
		    /////////////////////////////////
		    // Brute Force Optimization  //
		    /////////////////////////////////
		    if (args[i].equals("-bruteforce")) {
			if (verbose) {
			    System.out.println("Brute Force Optimization");
			}
			
			++i;
			BufferedReader reader = new BufferedReader(new FileReader(args[i]));
			
			String classifierType= reader.readLine().replaceAll("\\s+","");
			int topK = Integer.parseInt(reader.readLine().replaceAll("\\s+",""));
			int noOfHPs = Integer.parseInt(reader.readLine().replaceAll("\\s+",""));
			
			if (verbose) {
			    System.out.println("Classifier: " + classifierType);
			    System.out.println("Only top " + topK + " of the configurations are reported.");			    
			    System.out.println("Number of hyper-parameters: " + noOfHPs);

			}

			double[] lowerBounds = new double[noOfHPs];
			double[] upperBounds = new double[noOfHPs];
			String[] hpTypes = new String[noOfHPs];
			parseHPs(reader, noOfHPs, lowerBounds, upperBounds, hpTypes);
		
			ArrayList<HPConfiguration> allConfigurations = new ArrayList<HPConfiguration>();
 	
			String line;
			while((line = reader.readLine()) != null) {
			    line = line.replaceAll("\\s+"," ");
			    if (!line.equals("")) {
				String[] values = line.split("\\s+");
				HyperParameter[] hps = new HyperParameter[noOfHPs];
				for (int j = 0; j < noOfHPs; ++j) { 
				    if (hpTypes[j].equals("D")) {
					hps[j] = new DiscreteHP((int)lowerBounds[j], (int)upperBounds[j], 
								Integer.parseInt(values[j]));
				    }
				    else if (hpTypes[j].equals("C")) {
					hps[j] = new ContinuousHP(lowerBounds[j], upperBounds[j], 
								Double.parseDouble(values[j]));
				    }
				    else {
					System.err.println("Unknown hp type in file.");
					usage();
				    }
				}
				allConfigurations.add(new HPConfiguration(hps));
			    }
			}
			
			//////////////////////////////////
			// Classifier Type              //
			//////////////////////////////////
			if (classifierType.equals("knn")) {
			    classifier = new KNNClassifier((int)lowerBounds[0], (int)upperBounds[0]);
			}
			else if (classifierType.equals("gsvm")) {
			    classifier = new GaussianSVMClassifier(lowerBounds[0], upperBounds[0],
								   lowerBounds[1], upperBounds[1]);
			}
			else if (classifierType.equals("rf")) {
			    classifier = new RandomForestClassifier((int)lowerBounds[0], (int)upperBounds[0],
								    (int)lowerBounds[1], (int)upperBounds[1]);
			}
			
			else {
			    System.err.println("Unknown classifier.");
			    usage();
			}

			hpOptimizer = new BruteForceHPOptimizer(dataset, classifier,
								allConfigurations, topK);
		    }

		    ////////////////////////////////
		    // Hierarchical Optimization  //
		    ////////////////////////////////
		    else if (args[i].equals("-hierar")) {
			if (verbose) {
			    System.out.println("Hierarchical Optimization.");
			}
			
			++i;
			BufferedReader reader = new BufferedReader(new FileReader(args[i]));

			String classifierType = reader.readLine().replaceAll("\\s+","");
			int noOfHPs = Integer.parseInt(reader.readLine().replaceAll("\\s+",""));

			if (verbose) {
			    System.out.println("Classifier: " + classifierType);
			    System.out.println("Number of hyper-parameters: " + noOfHPs);
			}

			double[] lowerBounds = new double[noOfHPs];
			double[] upperBounds = new double[noOfHPs];
			String[] hpTypes = new String[noOfHPs];

			parseHPs(reader, noOfHPs, lowerBounds, upperBounds, hpTypes);

			int initialConfsToTry = 
			    Integer.parseInt(reader.readLine().replaceAll("\\s+",""));
			int noOfLevels = 
			    Integer.parseInt(reader.readLine().replaceAll("\\s+",""));
			
			if (verbose) {
			    System.out.println("Number of levels: " + noOfLevels);			
			    System.out.println("Number of trials in the bottom level: " + 
					       initialConfsToTry);
			}
			
			int[] topKs = new int[noOfLevels];
			double[] portions = new double[noOfLevels];
			
			for (int j = 0; j < noOfLevels; ++j) {
			    String[] values = reader.readLine().split("\\s+");
			    topKs[j] = Integer.parseInt(values[0]);
			    portions[j] = Double.parseDouble(values[1]);
			}

			LocalSearcher localSearcher = null;

			///////////////////////////////
			// Simulated Annealing    //
			///////////////////////////////
			String[] values = reader.readLine().replaceAll("\\s+"," ").split("\\s+");
			if (values[0].equals("simulatedannealer")) {
			    if (verbose) {
				System.out.println("Using simulated annealing");
			    }
			    double temperature = Double.parseDouble(values[1]);
			    double coolingRate = Double.parseDouble(values[2]);
			    localSearcher = new SimulatedAnnealer(temperature, coolingRate);
			}
			else {
			    System.err.println("Unknown local searcher.");
			    usage();
			}

			if (classifierType.equals("knn")) {
			    classifier = new KNNClassifier((int)lowerBounds[0], (int)upperBounds[0]);
			}
			
			else if (classifierType.equals("gsvm")) {
			    classifier = new GaussianSVMClassifier(lowerBounds[0], upperBounds[0],
								   lowerBounds[1], upperBounds[1]);
			}
			else if (classifierType.equals("rf")) {
			    classifier = new RandomForestClassifier((int)lowerBounds[0], (int)upperBounds[0],
								    (int)lowerBounds[1], (int)upperBounds[1]);
			}

			else {
			    System.err.println("Unknown classifier");
			    usage();
			}

			hpOptimizer = new HierarchicalHPOptimizer(dataset, classifier,
								  localSearcher, 
								  initialConfsToTry, 
								  topKs, portions);
		    }
		    else {
			usage();
		    }
		}
	    }

	    if (dataset == null || classifier == null) {
		usage();
	    }

	    /////////////////////
	    // Execution       //
	    /////////////////////
	    
	    ////////////////////////////
	    // Single Run Execution //
	    ////////////////////////////
	    if (singleRunMode) {
		if (verbose) {
		    System.out.println("Single Run.");
		}
		startTime = System.currentTimeMillis();
		double acc = classifier.evaluateClassifier(dataset, initialConfiguration);
		endTime = System.currentTimeMillis();
		System.out.println(initialConfiguration.toString());
		System.out.println("Accuracy: " + acc);
	    }
	
	    
	    ///////////////////////////////////
	    // Optimization Run Execution //
	    ///////////////////////////////////
	    else if (optimizationMode) {
		if (verbose) {
		    System.out.println("Optimization Mode.");
		}
		
		startTime = System.currentTimeMillis();
		PriorityQueue<ConfigurationAccuracyPair> topKConfigurations = hpOptimizer
		    .optimize();
		
		PriorityQueue<ConfigurationAccuracyPair> sortedConfigurations 
		    = new PriorityQueue<ConfigurationAccuracyPair>();
		
		if (verbose) { 
		    while (topKConfigurations.size() > 0) {
			ConfigurationAccuracyPair pair = topKConfigurations.poll();
			double acc = classifier.evaluateClassifier(dataset, pair.getConf());
			sortedConfigurations.add(new ConfigurationAccuracyPair(pair.getConf(), acc));
		    }
		    endTime = System.currentTimeMillis();
		    Utils.print(sortedConfigurations);
		}
		
		else {
		    ConfigurationAccuracyPair topPair = topKConfigurations.poll();
		    double acc = classifier.evaluateClassifier(dataset, topPair.getConf());
		    endTime = System.currentTimeMillis(); 
		    System.out.println("Best Configuration:\n\t " + topPair.getConf().toString());
		    System.out.println("Accuracy of best configuration: " + acc);
		}
	    }
	    else {
		usage();
	    }
	    long totalTimeMs = (endTime - startTime);
	    double totalTimeSec = totalTimeMs / 1000.0;
	    System.out.println("Total Time: " + totalTimeSec + " seconds.");
	} catch (Exception e) {
	    usage();
	}
    }
}
